package com.youtills.core.config;

import com.youtills.core.lang.KeyValuePair;
import com.youtills.core.lang.StringUtil;
import com.youtills.core.io.IoInputStream;
import com.youtills.core.io.IoUtil;
import com.youtills.core.lang.InstanceMaker;

import java.io.IOException;
import java.util.*;

/**
 * Created by IntelliJ IDEA.
 * User: ravi
 * Date: 9/14/12 6:53 PM
 * To change this template use File | Settings | File Templates.
 */
public final class ConfigurationLoader {

    public static final String DEFAULT_APPLICATION = "youtills";
    public static final String DEFAULT_ENVIRONMENT = "local";
    public static final String CONFIG_PREFIX = "youtills.config.";
    public static final String CONFIG_APP_NAME = CONFIG_PREFIX + "app.name";
    public static final String CONFIG_ENV_NAME = CONFIG_PREFIX + "env.name";



    public static final String CONFIG_SOURCE = "Youtills Configuration Loader";

    public static final String CONFIG_DEFAULT_PROPERTIES_FILE = CONFIG_PREFIX + "default.properties";
    public static final String CONFIG_ENVIRONMENT = CONFIG_PREFIX + "environment";
    public static final String CONFIG_PROVIDER_PREFIX = CONFIG_PREFIX + "provider.";

    private Properties properties;
    private long modifiedTime;
    private final String environment;
    private String defaultPropertiesFile;

    private List<IConfigProvider> configProviders = new ArrayList<IConfigProvider>();
    private List<KeyValuePair<String, String>>configProviderProps = new ArrayList<KeyValuePair<String,String>>();

    private static final ConfigurationLoader instance = new ConfigurationLoader();

    private ConfigurationLoader() {
        this (1);
    }

    private ConfigurationLoader(int a) {

        this.properties = new Properties();

        String _environment = System.getProperty(CONFIG_ENVIRONMENT);

        if(!StringUtil.isEmpty(_environment)){
            this.environment = _environment;
        }else{
            this.environment = DEFAULT_ENVIRONMENT;
        }

        String defaultProperties = System.getProperty(CONFIG_DEFAULT_PROPERTIES_FILE);

        if(!StringUtil.isEmpty(defaultProperties)){
            defaultPropertiesFile = defaultProperties;
        }else{
            defaultPropertiesFile = CONFIG_DEFAULT_PROPERTIES_FILE;
        }

        load(defaultPropertiesFile, false);

        loadProviders();

        modifiedTime = System.currentTimeMillis();

    }

    private void load(String propertiesFile, boolean reload){
        try {
            Properties props = new Properties();
            IoInputStream stream = IoUtil.getIoStream(propertiesFile);
            if(stream != null && stream.getInputStream() != null){
                props.load(stream.getInputStream());
            }
            addProperties(props, reload);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private synchronized void addProperties(Properties props, boolean reload){
        Properties newProps = new Properties();
        if(!reload){
            newProps.putAll(props);
            newProps.putAll(properties);
        }else{
            newProps.putAll(properties);
            newProps.putAll(props);
        }
        properties = newProps;
        modifiedTime = System.currentTimeMillis();
    }

    private void loadProviders(){

        for(String key : properties.stringPropertyNames()){
            if(!StringUtil.isEmpty(key) && key.startsWith(CONFIG_PROVIDER_PREFIX)){
                String value = properties.getProperty(key);
                if(!StringUtil.isEmpty(value)){
                    configProviderProps.add(new KeyValuePair<String, String>(key, value));
                }
            }
        }

        //Add system property provider.
//        IConfigProvider provider = new SystemPropertyProvider();
//        provider.init(environment);
        configProviders.add(new SystemPropertyProvider());


        if(configProviderProps.size() > 0){
            Collections.sort(configProviderProps, new Comparator<KeyValuePair<String, String>>() {
                public int compare(KeyValuePair<String, String> o1, KeyValuePair<String, String> o2) {
                    return o1.getKey().compareTo(o2.getKey());
                }
            });

            InstanceMaker<IConfigProvider> providerMaker = new InstanceMaker<IConfigProvider>();

            for(KeyValuePair<String, String> kv : configProviderProps){
                try{
                    IConfigProvider provider = providerMaker.getInstanceOf(kv.getValue());
                    provider.init(environment);
                    configProviders.add(provider);
                }catch(RuntimeException re){

                }
            }
        }else {
            IConfigProvider provider = new PropertyFileProvider();
            provider.init(environment);
            configProviders.add(provider);
        }
    }

    private synchronized void addSource(String source, boolean reload){
        if(configProviders != null){
            for(IConfigProvider provider : configProviders){
                provider.addProperties(source);
            }
        }
    }

    public static void addConfigurationSource(String source, boolean reload) {


    }

    public static void loadProperties(String propertiesFile, boolean reload) {
        instance.load(propertiesFile, reload);
    }

    public static long getModifiedTime() {
        return instance.modifiedTime;
    }

    public static String getEnvironment(){
        return instance.environment;
    }

    private IConfigValue configValue(String key){

        IConfigValue value = null;
        for(IConfigProvider provider : configProviders){
            value = provider.getConfigValue(key);
            if(value != null){
                break;
            }
        }
//        return properties.getProperty(key, defaultValue);
        if(value == null){
            String prop = properties.getProperty(key);
            if(prop != null){
                value = new ConfigValue(prop, CONFIG_SOURCE);
            }

        }
        return value;
    }


    private Set<String> propertyNames(){
        Set<String> propertyNames = new HashSet<String>();

        propertyNames.addAll(properties.stringPropertyNames());

        for(IConfigProvider provider : configProviders){
            Set<String> names = provider.getConfigNames();
            if(names != null){
                propertyNames.addAll(names);
            }
        }
        return propertyNames;

    }

    public static IConfigValue getConfigValue(String key){
        return instance.configValue(key);
    }

    public static Set<String> getPropertyNames(){
        return instance.propertyNames();
    }
}
